package com.hupu.jrs.service

import android.app.Service
import android.content.Intent
import android.net.Uri
import android.os.IBinder
import android.text.TextUtils
import android.util.Log
import com.alibaba.fastjson.JSON
import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.imagepipeline.cache.DefaultCacheKeyFactory
import com.facebook.imagepipeline.core.ImagePipelineFactory
import com.facebook.imagepipeline.request.ImageRequest
import com.hupu.jrs.BuildConfig
import com.hupu.jrs.Logger
import com.hupu.jrs.MyApplication
import com.hupu.jrs.api.forum.ForumApi
import com.hupu.jrs.components.notifier.OfflineNotifier
import com.hupu.jrs.components.okhttp.OkHttpHelper
import com.hupu.jrs.injector.module.ServiceModule
import com.hupu.jrs.util.ConfigUtil
import com.hupu.jrs.util.FileUtil
import com.hupu.jrs.util.NetWorkUtil
import com.hupu.jrs.util.SettingPrefUtil
import java.io.File
import java.util.ArrayList
import java.util.HashMap
import java.util.concurrent.LinkedBlockingQueue
import java.util.regex.Pattern
import javax.inject.Inject
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import rx.subscriptions.CompositeSubscription

/**
 * Created by sll on 2016/5/30.
 */
class OffLineService : Service() {

    @Inject
    internal var mForumApi: ForumApi? = null
    @Inject
    internal var mForumDao: ForumDao? = null
    @Inject
    internal var mThreadDao: ThreadDao? = null
    @Inject
    internal var mOfflineNotifier: OfflineNotifier? = null
    @Inject
    internal var mReplyDao: ThreadReplyDao? = null
    @Inject
    internal var mThreadInfoDao: ThreadInfoDao? = null
    @Inject
    internal var mOkHttpHelper: OkHttpHelper? = null

    private var mCurrentStatus = INIT

    private var forums: List<Forum>? = null
    private var unOfflineForums: MutableList<Forum>? = null
    private var offlineThreadsCount = 0// 离线的帖子数量
    private var offlineRepliesCount = 0 //离线的回复数量

    private var offlineThreadsLength: Long = 0// 离线的帖子总流量大小
    private var offlineRepliesLength: Long = 0
    private var offlinePictureLength: Long = 0// 离线的图片总流量大小

    private var offlinePictureCount = 0// 离线的图片数量

    private val threadList = LinkedBlockingQueue<Thread>()// 线程安全队列

    private val lastTidMap = HashMap<String, String>()
    private val lastStmMap = HashMap<String, String>()
    private val threadCountMap = HashMap<String, Int>()

    private val mCompositeSubscription = CompositeSubscription()

    private val isCanceled: Boolean
        get() = mCurrentStatus == CANCEL || mCurrentStatus == FINISHED

    override fun onBind(intent: Intent): IBinder? {
        return null
    }

    override fun onCreate() {
        super.onCreate()
        Logger.d("服务初始化")
        DaggerServiceComponent.builder()
                .serviceModule(ServiceModule(this))
                .applicationComponent((application as MyApplication).applicationComponent)
                .build()
                .inject(this)
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        if (intent == null || TextUtils.isEmpty(intent.action)) {
            return super.onStartCommand(intent, flags, startId)
        }
        val action = intent.action
        if (action == START_DOWNLOAD) {
            if (mCurrentStatus == INIT) {
                forums = intent.getSerializableExtra(EXTRA_FORUMS) as List<Forum>
                prepareOffline()
            } else {
                //ignore
                Logger.d("服务已启动，忽略请求")
            }
        }
        return super.onStartCommand(intent, flags, startId)
    }

    private fun prepareOffline() {
        if (forums == null || forums!!.isEmpty()) {
            return
        }
        mCurrentStatus = PREPARE
        unOfflineForums = ArrayList()
        unOfflineForums!!.addAll(forums!!)
        for (forum in unOfflineForums!!) {
            if (isCanceled) {
                return
            }
            getThreadList(forum, "", "")
        }
    }

    private fun getThreadList(forum: Forum, lastTid: String, lastStm: String) {
        val fid = forum.fid
        if (threadCountMap[fid] != null && threadCountMap[fid] >= TOTAL_COUNT) {
            mOfflineNotifier!!.notifyThreads(forum, offlineThreadsLength)
            unOfflineForums!!.remove(forum)
            prepareReplies()
            return
        }
        val mSubscription = mForumApi!!.getThreadsList(fid!!, lastTid, lastStm,
                SettingPrefUtil.getThreadSort(this@OffLineService))
                .doOnNext { threadListData ->
                    if (threadListData != null && threadListData.result != null) {
                        val threads = threadListData.result!!.data
                        offlineThreadsCount += threads!!.size
                        offlineThreadsLength += JSON.toJSONString(threadListData).length.toLong()
                        lastStmMap.put(fid, threadListData.result!!.stamp)
                        threadList.addAll(threads)
                        lastTidMap.put(fid, threads[threads.size - 1].getTid())
                        var count: Int? = threadCountMap[fid]
                        if (count == null) {
                            count = 0
                        }
                        threadCountMap.put(fid, count + threads.size)
                        if (!threads.isEmpty()) {
                            val type = Integer.valueOf(threads[0].getFid())
                            if (TextUtils.isEmpty(lastTid) && TextUtils.isEmpty(lastStm)) {
                                mThreadDao!!.queryBuilder()
                                        .where(ThreadDao.Properties.Type.eq(type))
                                        .buildDelete()
                                        .executeDeleteWithoutDetachingEntities()
                            }
                            for (thread in threads) {
                                if (mThreadDao!!.queryBuilder()
                                        .where(ThreadDao.Properties.Tid.eq(thread.getTid()),
                                                ThreadDao.Properties.Type.eq(type))
                                        .count() === 0) {
                                    thread.setType(type)
                                    mThreadDao!!.insert(thread)
                                }
                            }
                        }
                    }
                }
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe({ getThreadList(forum, lastTidMap[fid], lastStmMap[fid]) }) { throwable ->
                    throwable.printStackTrace()
                    getThreadList(forum, lastTidMap[fid], lastStmMap[fid])
                }
        mCompositeSubscription.add(mSubscription)
    }

    private fun prepareReplies() {
        if (!unOfflineForums!!.isEmpty()) {
            return
        }
        mOfflineNotifier!!.notifyThreadsSuccess(forums!!.size, offlineThreadsCount, offlineThreadsLength)
        if (threadList.isEmpty()) {
            stopSelf()
        } else {
            for (thread in threadList) {
                val mSubscription = Observable.zip(mForumApi!!.getThreadInfo(thread.getTid(), thread.getFid(), 1, ""),
                        mForumApi!!.getThreadReplyList(thread.getTid(), thread.getFid(), 1),
                        mForumApi!!.getThreadLightReplyList(thread.getTid(), thread.getFid())
                ) { threadInfo, threadReplyData, threadLightReplyData ->
                    if (threadInfo != null) {
                        offlineRepliesLength += JSON.toJSONString(threadInfo).length.toLong()
                        threadInfo.forumName = threadInfo.forum!!.name
                        mThreadInfoDao!!.queryBuilder()
                                .where(ThreadInfoDao.Properties.Tid.eq(thread.getTid()))
                                .buildDelete()
                                .executeDeleteWithoutDetachingEntities()
                        mThreadInfoDao!!.insert(threadInfo)
                        transImgToLocal(threadInfo.content)
                    }

                    if (threadReplyData != null && threadReplyData.status == 200) {
                        offlineRepliesLength += JSON.toJSONString(threadReplyData).length.toLong()
                        val result = threadReplyData.result
                        if (result != null && !result.list!!.isEmpty()) {
                            for (reply in result.list!!) {
                                reply.tid = thread.getTid()
                                reply.pageIndex = 1
                                saveReply(reply, false)
                            }
                        }
                    }

                    if (threadLightReplyData != null && threadLightReplyData.status == 200) {
                        offlineRepliesLength += JSON.toJSONString(threadLightReplyData).length.toLong()
                        if (!threadLightReplyData.list!!.isEmpty()) {
                            for (reply in threadLightReplyData.list!!) {
                                saveReply(reply, true)
                            }
                        }
                    }
                    true
                }
                        .subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread())
                        .subscribe({ offlineReplyComplete(thread) }) { throwable ->
                            throwable.printStackTrace()
                            offlineReplyComplete(thread)
                        }
                mCompositeSubscription.add(mSubscription)
            }
        }
    }

    private fun offlineReplyComplete(thread: Thread) {
        mOfflineNotifier!!.notifyReplies(thread, offlineRepliesLength)
        threadList.remove(thread)
        if (threadList.isEmpty()) {
            Logger.d("mThreads.isEmpty():" + offlineRepliesCount)
            mOfflineNotifier!!.notifyRepliesSuccess(offlineThreadsCount, offlineRepliesCount,
                    offlineRepliesLength)
            stopSelf()
        }
        stopSelfIfCan()
    }

    private fun stopSelfIfCan(): Boolean {
        if (isCanceled) {
            Logger.d("isCanceled")
            stopSelf()
            return true
        }

        if (!NetWorkUtil.isWifiConnected(this)) {
            Logger.d("isWiFi")
            mCurrentStatus = CANCEL
            stopSelf()
            return true
        }
        return false
    }

    private fun saveReply(reply: ThreadReply, isLight: Boolean) {
        offlineRepliesCount++
        if (!reply.quote!!.isEmpty()) {
            val quote = reply.quote!![0]
            reply.quoteHeader = quote.header!!.get(0)
            if (!TextUtils.isEmpty(quote.togglecontent)) {
                reply.quoteToggle = quote.togglecontent
            }
            reply.quoteContent = quote.content
        }
        reply.isLight = isLight
        mReplyDao!!.queryBuilder()
                .where(ThreadReplyDao.Properties.Pid.eq(reply.pid))
                .buildDelete()
                .executeDeleteWithoutDetachingEntities()
        mReplyDao!!.insert(reply)

        transImgToLocal(reply.content)
        transImgToLocal(reply.quoteContent)
        transGifToLocal(reply.content)
        transGifToLocal(reply.quoteContent)
    }

    private fun transImgToLocal(content: String?) {
        if (TextUtils.isEmpty(content)) {
            return
        }
        val pattern = Pattern.compile("<img(.+?)data_url=\"(.+?)\"(.+?)src=\"(.+?)\"(.+?)>")
        val matcher = pattern.matcher(content!!)
        while (matcher.find()) {
            val thumb = matcher.group(4)
            val original = matcher.group(2)
            cacheImage(thumb, original)
        }
        matcher.reset()
    }

    private fun transGifToLocal(content: String?) {
        if (TextUtils.isEmpty(content)) {
            return
        }
        val pattern = Pattern.compile("<img(.+?)data-gif=\"(.+?)\"(.+?)src=\"(.+?)\"(.+?)>")
        val matcher = pattern.matcher(content!!)
        while (matcher.find()) {
            val thumb = matcher.group(4)
            val original = matcher.group(2)
            cacheImage(thumb, original)
        }
        matcher.reset()
    }

    private fun cacheImage(thumb: String, original: String) {
        cacheImage(original)
        val localUrl = thumb.substring(thumb.lastIndexOf("/") + 1)
        val localPath = ConfigUtil.getCachePath() + File.separator + localUrl
        if (!FileUtil.exist(localPath)) {
            try {
                mOkHttpHelper!!.httpDownload(thumb, File(localPath))
            } catch (e: Exception) {
                Log.d("HtmlUtils", "图片下载失败:" + thumb)
            }

            offlinePictureCount++
            offlinePictureLength += File(localPath).length()
        }
    }

    private fun cacheImage(url: String) {
        if (!isImageDownloaded(Uri.parse(url))) {
            val imagePipeline = Fresco.getImagePipeline()
            val request = ImageRequest.fromUri(url)
            imagePipeline.prefetchToDiskCache(request, this)
            offlinePictureLength += request.sourceFile.length()
            offlinePictureCount++
        }
    }

    private fun isImageDownloaded(loadUri: Uri?): Boolean {
        if (loadUri == null) {
            return false
        }
        val cacheKey = DefaultCacheKeyFactory.getInstance()
                .getEncodedCacheKey(ImageRequest.fromUri(loadUri), null)
        return ImagePipelineFactory.getInstance().mainFileCache.hasKey(cacheKey) || ImagePipelineFactory.getInstance().smallImageFileCache.hasKey(cacheKey)
    }

    override fun onDestroy() {
        super.onDestroy()
        Logger.d("onDestroy")
        mOfflineNotifier!!.notifyPictureSuccess(offlinePictureCount, offlinePictureLength)
        mCurrentStatus = FINISHED
        if (mCompositeSubscription != null && !mCompositeSubscription.isUnsubscribed) {
            mCompositeSubscription.unsubscribe()
        }
    }

    companion object {
        val START_DOWNLOAD = BuildConfig.APPLICATION_ID + ".action.START_DOWNLOAD"
        val EXTRA_FORUMS = "forums"

        val INIT = 0
        val PREPARE = 1
        val LOAD_THREAD = 2
        val LOAD_REPLY = 3
        val LOAD_PICTURE = 4
        val CANCEL = 5
        val FINISHED = 6
        private val TOTAL_COUNT = 100
    }
}
